; The Moss bootloader
;  - Enables the A20 gate
;  - Enables protected mode
;  - Load the kernel
;  - Enables paging (higher half)

[bits 16]
[org 0x7C00]

 VStackAddr equ 0x7BFF
 BSectorAddr equ 0x7C00
 TempLoadAddr equ 0x7E00 ; About 600KB free after kernel
 KernelLoadAddr equ 0x200000
 KernelPDAddr equ 0x1FD000
 KernelPTAddr equ 0x1FE000
 KernelPT2Addr equ 0x1FF000
 HigherHalfAddr equ 0xC0000000

; Preserve the DOS header
 jmp start
 nop
 times 0x3B db 0
start:

; Reset floppy driver
 call reset_floppy

; Enabling A20
 call kbd_wait
 mov al, 0xD1
 out 0x64, al ; Tells the keyboard we want to write to it
 call kbd_wait
 mov al, 0xDF
 out 0x60, al ; Enable the A20 gate
 call kbd_wait

; Load 1/4 of first floppy
 cli
 mov bx, BSectorAddr >> 4
 mov word [VStackAddr-1], bx
 mov bx, 0
 mov byte [VStackAddr-2], bl
loadFloppyLoop:
 mov ch, bl
 shr ch, 1 ; Cylinder (track) = lba/sectorspertrack/numheads (eax/2)
 mov cl, 1 ; Sector
 mov dh, bl
 and dh, 0x1 ; Head = lba/sectorspertrack mod numheads (eax & 1)
 mov dl, 0 ; Drive
 mov bx, word [VStackAddr-1]
 mov es, bx ; Segment to load to
 add bx, 0x240 ; bytes per track >> 4
 mov word [VStackAddr-1], bx
 mov bx, 0 ; Offset to load to
 mov al, 18 ; Number of sectors to read
 call read_sector
 mov bl, byte [VStackAddr-2]
 inc bl
 mov byte [VStackAddr-2], bl
 cmp bl, 2 ; 1/4 of a 3.5" floppy = 720 sectors = 40 tracks
 jl loadFloppyLoop

; Set up the GDT descriptor for protected mode
 cli
 lgdt [gdt_desc]

; Enable protected mode - cr0 bit 0
 cli
 mov eax, cr0
 or eax, 0x1
 mov cr0, eax
 jmp 0x8:pm

; Various function that are skipped over in the jump to protected mode

; Funtion to wait for the keyboard to be available
kbd_wait:
 in al, 0x64
 test al, 2
 jne kbd_wait
 ret

gdt_desc:
 dw gdt_end - gdt - 1 ; Size of GDT
 dd gdt
gdt:
 db 0,0,0,0,0,0,0,0 ; NULL descriptor
; Code descriptor
 dw 0xFFFF ; Limit low word
 dw 0 ; Base - low word
 db 0 ; Base - middle byte
 db 10011010b ; Access byte
 db 11001111b ; Granularity byte
 db 0 ; Base - high byte

; Data descriptor:
 dw 0xFFFF ; Limit low word
 dw 0 ; Base - low word
 db 0 ; Base - middle byte
 db 10010010b ; Access byte
 db 11001111b ; Granularity byte
 db 0 ; Base - high byte
gdt_end:

; Resets the floppy drive
reset_floppy:
 mov ah, 0 ; Funtion number: reset floppy drive
 mov dl, 0 ; Drive
 int 0x13
 jc reset_floppy
 ret

; Load a floppy sector
;  al (sectors to read)
;  ch (cylinder)
;  cl (sector)
;  es:bx (seg:offset to load to)
;  dl (drive number)
;  dh (drive)
read_sector:
 mov ah, 0x02 ; Function number: read sectors
 int 0x13
 jc read_sector ; Tries again on error
 ret

[bits 32]
pm:

; Flush registers for GDT
 mov ax, 0x10
 mov ds, ax
 mov ss, ax
 mov es, ax

; Initializes the page directory and table
 mov eax, 0
pgInitLoop:
 mov ebx, eax
 shl ebx, 2 ; Each entry is four bytes long
 mov ecx, ebx
; Page directory entry
 add ebx, KernelPDAddr
 mov dword [ebx], 0 ; Marked as not-present
; Page table entry
 add ebx, KernelPTAddr - KernelPDAddr
 mov ecx, eax
 shl ecx, 12 ; Each page is 4KB - x1000000000000
 or ecx, 3 ; Marked as read/write, present
 mov dword [ebx], ecx
; Last page table entry
 add ebx, KernelPT2Addr - KernelPTAddr
 mov dword [ebx], 0 ; Marked as not-present
 inc eax
 cmp eax, 1023 ; 1024 entries in a page directory
 jle pgInitLoop
 ; The first directory entry points to the first page table and is marked read/write and present
 mov dword [KernelPDAddr], KernelPTAddr | 3
 ; The first page table also maps the first 4MB to the higher-half address
 mov dword [KernelPDAddr+((HigherHalfAddr/0x400000)*4)], KernelPTAddr | 3
 ; Map the page directory to the end of linear memory
 mov dword [KernelPT2Addr+(1023*4)], KernelPDAddr | 3
 mov dword [KernelPDAddr+(1023*4)], KernelPT2Addr | 3

 ; Enable paging
 mov eax, KernelPDAddr
 mov cr3, eax ; The page directory address goes in the cr3 register
 mov eax, cr0
 or eax, 0x80000000 ; Set the paging bit...
 mov cr0, eax ; ...and write the cr0 register. Without a triple fault, paging is enabled.

; Move the kernel to where it is supposed to be
 mov eax, 0
kernelMoveLoop:
 mov ebx, eax
 shl ebx, 2 ; dwords
 add ebx, TempLoadAddr
 mov ecx, dword [ebx]
 mov ebx, eax
 shl ebx, 2 ; dwords
 add ebx, KernelLoadAddr
 mov dword [ebx], ecx
 inc eax
 cmp eax, 4608 ; 4608 dwords loaded
 jle kernelMoveLoop

 jmp KernelLoadAddr + HigherHalfAddr

; Just in case
 cli
 hlt

times (510 - ($ - $$)) db 0
dw 0xAA55